/*
 * Copyright (c) 2004-2005 The Regents of The University of Michigan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Kevin Lim
 */

#include "base/intmath.hh"
#include "base/trace.hh"
#include "cpu/edge/pred/ctb.hh"

void 
DefaultCTB::init(unsigned _numEntries,
                      unsigned _blockShiftAmt,
                      unsigned _offsetLength)
{
    numEntries = _numEntries;

    blockShiftAmt = _blockShiftAmt;

    offsetLength = _offsetLength;	
	
    ctb.resize(numEntries);

    for(unsigned i = 0; i < numEntries; ++i) {
        ctb[i].call_target = 0;
	 ctb[i].call_target_hyst = 0;
	 ctb[i].call_updated = false;
	 ctb[i].ret_addr_offset = 0;
	 ctb[i].ret_addr_hyst = 0;
	 ctb[i].ret_updated = false;
    }

    ctbMask = numEntries - 1;

    ctbIdxLength = floorLog2(numEntries);

    offsetMask = (1 << offsetLength) - 1;

}
	
void
DefaultCTB::reset()
{
    for(unsigned i = 0; i < numEntries; ++i) {
        ctb[i].call_target = 0;
	 ctb[i].call_target_hyst = 0;
	 ctb[i].call_updated = false;
	 ctb[i].ret_addr_offset = 0;
	 ctb[i].ret_addr_hyst = 0;
	 ctb[i].ret_updated = false;
    }
}

unsigned
DefaultCTB::CTBIdxGen(Addr PC, int addr_space_ID, ExitID exitID)
{
    assert (addr_space_ID >= 0 && addr_space_ID <= 15);
    int reversed_as_id = ((addr_space_ID & 0x1) << 3)
                        | ((addr_space_ID & 0x2) << 1)
                        | ((addr_space_ID & 0x4) >> 1)
                        | ((addr_space_ID & 0x8) >> 3);

    assert (exitID >= 0 && exitID <= 7);
    int reversed_exit = ((exitID & 0x1) << 2)
                       | (exitID & 0x2)
                       | ((exitID & 0x4) >> 2);
    unsigned index = ((PC >> blockShiftAmt)
                      ^ ( (reversed_as_id << (ctbIdxLength - 4)) |
                          (reversed_exit << (ctbIdxLength - 7)) ) )
                     & ctbMask;
    return index;
}

Addr
DefaultCTB::lookupTarget(Addr PC,int addr_space_ID, ExitID pred_exitID)
{
    unsigned index = CTBIdxGen(PC, addr_space_ID, pred_exitID);
    CTBEntry ctbEntry = ctb[index];
    if(ctb[index].call_updated)
        DPRINTF(EdgePredUnit,"Block [PC:%#x]:CTB call updated before.\n",PC);
    else
	 DPRINTF(EdgePredUnit,"Block [PC:%#x]:CTB call first accessed.\n",PC);
    return ctbEntry.call_target << blockShiftAmt;
}

Addr
DefaultCTB::lookupRetAddr(Addr PC,int addr_space_ID, ExitID pred_exitID)
{
    unsigned index = CTBIdxGen(PC, addr_space_ID, pred_exitID);
    CTBEntry ctbEntry = ctb[index];
    Addr offset = ctbEntry.ret_addr_offset;
    if ((offset >> (offsetLength- 1)) & 0x1) // negative within those 9 bits
        offset = offset | (~offsetMask);
    if(ctb[index].ret_updated)
        DPRINTF(EdgePredUnit,"Block [PC:%#x]:CTB return updated before.\n",PC);
    else
	 DPRINTF(EdgePredUnit,"Block [PC:%#x]:CTB return first accessed.\n",PC);
    DPRINTF(EdgePredUnit,"Block [PC:%#x]:Return address is %#x. Offset is %#x.\n",PC,
		PC + (offset<<blockShiftAmt),offset);
    return (PC >> blockShiftAmt) + offset;
}

void
DefaultCTB::update(unsigned index, Addr target)
{
    CTBEntry ctbEntry = ctb[index];
    CTBEntry new_ctbEntry;
    target = target >> blockShiftAmt;
    new_ctbEntry.call_target = ctbEntry.call_target_hyst ? ctbEntry.call_target : target;
    new_ctbEntry.call_target_hyst = (ctbEntry.call_target == target) ;
    new_ctbEntry.call_updated = true;
    new_ctbEntry.ret_addr_offset = ctbEntry.ret_addr_offset;
    new_ctbEntry.ret_addr_hyst = ctbEntry.ret_addr_hyst;
    new_ctbEntry.ret_updated = ctbEntry.ret_updated;
    ctb[index] = new_ctbEntry;
    DPRINTF(EdgePredUnit,"CTB call target update:%#x.\n",target<<blockShiftAmt);
}


void
DefaultCTB::update(unsigned index, Addr block_addr, Addr target)
{
    CTBEntry ctbEntry = ctb[index];
    CTBEntry new_ctbEntry;
    Addr offset = ((target - block_addr) >> blockShiftAmt) & offsetMask ;
    new_ctbEntry.ret_addr_offset = ctbEntry.ret_addr_hyst ? ctbEntry.ret_addr_offset : offset;
    new_ctbEntry.ret_addr_hyst = (ctbEntry.ret_addr_offset == offset) ;
    new_ctbEntry.ret_updated = true;
    new_ctbEntry.call_target = ctbEntry.call_target;
    new_ctbEntry.call_target_hyst = ctbEntry.call_target_hyst;
    new_ctbEntry.call_updated = ctbEntry.call_updated;
    ctb[index] = new_ctbEntry;
    DPRINTF(EdgePredUnit,"Block [PC:%#x]: Return updated. Target = %#x.\n"
		"Offset is %#x(%#x).\n", block_addr,target,offset,new_ctbEntry.ret_addr_offset);
 }


